library(tidyverse)
library(here)
library(vegan)
library(RColorBrewer)
library(broom)
source(here("util_functions.R"))

Read data

criteria_path <- here("data/disease_criteria")
# clean_diagnosis <- function(diagnosis){
#   diagnosis <- str_extract(diagnosis, "[a-zA-Z].*") # Remove all non-characters from beginning (e.g. "1)", " - ")
#   # diagnosis <- str_to_sentence(diagnosis) # Capitalize only the first letter 
#   diagnosis <- tolower(diagnosis) 
#   diagnosis <- gsub("\\([a-z]*\\)","", diagnosis)
#   diagnosis <- gsub("\\.","", diagnosis)
#   diagnosis <- trimws(diagnosis)
#   return(diagnosis)
# }
# 
# process_diagnoses <- function(resp){
#   diagnoses <- str_split(resp, "\n") %>% 
#     unlist() %>% 
#     clean_diagnosis()
#   return(diagnoses)
# }

parse_diagnoses <- function(sim_results){
  
  tibble(criteria = rownames(sim_results)) %>% 
    mutate(data = map(criteria, function(c){
      do.call(rbind, sim_results[c,]) %>% 
        mutate(diagnosis = map(diagnoses, process_diagnoses)) %>% 
        unnest(diagnosis) %>% 
        select(diagnosis)
      })) %>% 
    unnest(data)
         
}
# Read in ChatGPT output data for additional conditions and parse
df <- tibble(file = list.files(here("data/chatgpt_output/other_conditions"), full.names = T))%>% 
  mutate(data = map(file, readRDS)) %>% 
  mutate(data = map(data, parse_diagnoses)) %>% 
  unnest(data) %>% 
  select(-file)

# Read in original MCAS chatGPT output and append to data from additional conditions
df <- read_csv(here("data/compiled_mcas_chatgpt_diagnoses.csv")) %>% 
  mutate(criteria = gsub("consensus", "mcas_", consensus)) %>% 
  select(criteria, diagnosis) %>% 
  bind_rows(df) 
Rows: 38029 Columns: 2── Column specification ──────────────────────────────────────────────────────
Delimiter: ","
chr (2): diagnosis, consensus
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
df

Plot distributions

# Rank abundance plot
custom_pal <- RColorBrewer::brewer.pal(7, "Set1")[-6]

df %>% 
  count(criteria, diagnosis, sort = T) %>% 
  group_by(criteria) %>% 
  mutate(freq = n/sum(n)) %>% 
  arrange(desc(freq), .by_group = T) %>% 
  mutate(rank = 1:n()) %>% 
  select(criteria, freq, rank) %>% 
  mutate(cs = cumsum(freq)) %>% 
  filter(rank <= 50) %>% 
  mutate(criteria = toupper(criteria)) %>% 
  ggplot(aes(x = rank, y = freq, color = factor(criteria)))+
    geom_line() +
    theme_classic()+
    scale_color_manual(values = custom_pal)+
  scale_x_continuous(expand = c(0,0))+
  scale_y_continuous(expand = c(0,0)) +
  labs(x = "Diagnosis rank", y = "Diagnosis frequency", color = "")

Calculate and plot diversity

# Calculate diversity values for diagnosis distributions
diagnosis_table <- table(df$criteria, df$diagnosis)
div_diag  <- vegan::diversity(diagnosis_table)
div_diag  
 aha_kawasaki eular_acr_sle        mcas_1        mcas_2      migraine 
     4.884424      4.878526      4.912265      6.374465      4.231021 
    slicc_sle 
     4.566551 
# Plot diversity values
broom::tidy(div_diag) %>% 
  mutate(names = toupper(names)) %>% 
  ggplot(aes(x=names, y = x))+
  geom_bar(stat = "identity") +
  theme_bw() +
  labs(x = "", y = "Shannon diversity") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))
Warning: 'tidy.numeric' is deprecated.
See help("Deprecated")

Statistical testing

All group randomization

pairwise_div_difference <- function(df){
  df_div <- enframe(vegan::diversity(table(df$criteria, df$diagnosis)))
  df_diff <- data.frame(t(combn(unique(sort(df$criteria)),2))) %>% 
    left_join(df_div, by = c("X1"="name")) %>% 
    left_join(df_div, by = c("X2"="name")) %>% 
    unite("pair", X1, X2, sep = ".") %>%
    mutate(entropy_difference = value.x - value.y) %>% 
    select(-contains("value"))
  deframe(df_diff)
}

# pairwise_div_difference(df)
enframe(pairwise_div_difference(df), name = "pair", value = "entropy_difference")
pairwise_div_iteration <- function(){
  df %>% 
    count(criteria) %>% 
    mutate(diagnosis = map(n, function(x) sample(df$diagnosis, x, replace = T))) %>% 
    select(-n) %>% 
    unnest(diagnosis) %>% 
    pairwise_div_difference(.)
}

pairwise_div_iteration()
aha_kawasaki.eular_acr_sle        aha_kawasaki.mcas_1 
             -2.358099e-03              -2.015759e-02 
       aha_kawasaki.mcas_2      aha_kawasaki.migraine 
              1.376623e-02              -2.365815e-03 
    aha_kawasaki.slicc_sle       eular_acr_sle.mcas_1 
             -1.370551e-02              -1.779949e-02 
      eular_acr_sle.mcas_2     eular_acr_sle.migraine 
              1.612433e-02              -7.715884e-06 
   eular_acr_sle.slicc_sle              mcas_1.mcas_2 
             -1.134741e-02               3.392382e-02 
           mcas_1.migraine           mcas_1.slicc_sle 
              1.779177e-02               6.452076e-03 
           mcas_2.migraine           mcas_2.slicc_sle 
             -1.613205e-02              -2.747174e-02 
        migraine.slicc_sle 
             -1.133970e-02 
set.seed(1234)
pairs_permutation_distributions <- data.frame(t(replicate(10000, pairwise_div_iteration())))
sapply(combn(unique(sort(df$criteria)),2, simplify = F), paste, collapse = ".")
 [1] "aha_kawasaki.eular_acr_sle" "aha_kawasaki.mcas_1"        "aha_kawasaki.mcas_2"        "aha_kawasaki.migraine"      "aha_kawasaki.slicc_sle"     "eular_acr_sle.mcas_1"      
 [7] "eular_acr_sle.mcas_2"       "eular_acr_sle.migraine"     "eular_acr_sle.slicc_sle"    "mcas_1.mcas_2"              "mcas_1.migraine"            "mcas_1.slicc_sle"          
[13] "mcas_2.migraine"            "mcas_2.slicc_sle"           "migraine.slicc_sle"        
names(pairs_permutation_distributions)
 [1] "aha_kawasaki.eular_acr_sle" "aha_kawasaki.mcas_1"        "aha_kawasaki.mcas_2"        "aha_kawasaki.migraine"      "aha_kawasaki.slicc_sle"     "eular_acr_sle.mcas_1"      
 [7] "eular_acr_sle.mcas_2"       "eular_acr_sle.migraine"     "eular_acr_sle.slicc_sle"    "mcas_1.mcas_2"              "mcas_1.migraine"            "mcas_1.slicc_sle"          
[13] "mcas_2.migraine"            "mcas_2.slicc_sle"           "migraine.slicc_sle"        
pairs_entropy_diff <- enframe(pairwise_div_difference(df), name = "pair", value = "entropy_difference") %>% 
  mutate(entropy_difference = ifelse(entropy_difference >=0, 0-entropy_difference, entropy_difference))
pairs_entropy_diff
pairs_permutation_distributions %>% 
  pivot_longer(everything(), names_to = "pair", values_to = "entropy_difference") %>% 
  ggplot(aes(x = entropy_difference)) +
  geom_histogram(binwidth = 0.01)+
  geom_vline(data = pairs_entropy_diff, aes(xintercept = entropy_difference), color = "red")+
  facet_wrap(~pair, scales = "free")+
  theme_classic()

# Calculate p-values for diversity differences based on the quantile of the 
# diversity difference value compared to the null hypothesis diversity distribution values
# Pairwise comparison adjusted
entropy_diff_table <- data.frame(pairs_permutation_distributions) %>% 
  pivot_longer(everything(), names_to = "pair", values_to = "entropy_difference") %>% 
  group_by(pair) %>% 
  summarise(entropy_difference_distribution = list(entropy_difference)) %>% 
  right_join(pairs_entropy_diff) %>% 
  mutate(p_value = map2_dbl(entropy_difference, entropy_difference_distribution, ~ecdf(.y)(.x))) %>% 
  mutate(p_adj = p.adjust(p_value, method = "bonferroni", n = n()))
Joining with `by = join_by(pair)`
entropy_diff_table
# Plot diversity values
broom::tidy(div_diag) %>% 
  mutate(names = tolower(names)) %>% 
  ggplot(aes(x=names, y = x))+
  geom_bar(stat = "identity") +
  ggpubr::stat_pvalue_manual(
    entropy_diff_table %>% separate(pair, into = c("group1", "group2"), sep = "\\."),
    label= "p_adj",
    y.position = 7, step.increase = 0.2
  ) +
  theme_bw() +
  labs(x = "", y = "Shannon diversity") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))
Warning: 'tidy.numeric' is deprecated.
See help("Deprecated")

Pairwise randomization

pairs_list <- as.list(as.data.frame(combn(unique(df$criteria),2)))
names(pairs_list) <- sapply(pairs_list, paste, collapse = ".")
pairs_list
# Create function that subsets df of all diagnoses for a criteria based on a pair of two criteria to compare
# Then randomizes the diagnoses across the two criteria, 
# measures the resulting diversity of each criteria's diagnoses
# and calculates a difference in the diversity value
# randomize can be set F to obtain original difference in diversity w/o randomization
# To be used with replicate to generate a null hypothesis distribution 
permutation_pair_iteration <- function(pair, randomize = T){
  df <- df %>% 
    filter(criteria %in% pair) 
  if (randomize == T){
    df <- df %>% mutate(diagnosis = sample(diagnosis, n()))
  }
  div <- vegan::diversity(table(df$criteria, df$diagnosis))
  unname(div[1] - div[2])
}

replicate(10,permutation_pair_iteration(pairs_list[[1]], randomize = T))
replicate(10,permutation_pair_iteration(pairs_list[[1]], randomize = F))
# # Create a null hypothesis distribution for the difference in diversity
# # For all pairs of criteria

# set.seed(1234)
# pairs_permutation_distributions <- lapply(pairs_list, function(x) replicate(10000, permutation_pair_iteration(x)))
# names(pairs_permutation_distributions) <- lapply(pairs_list, function(x) paste(x, collapse = "-"))
# data.frame(pairs_permutation_distributions)
# write_csv(data.frame(pairs_permutation_distributions), here("data/criteria_pairwise_null_difference_distributions.csv"))

pairs_permutation_distributions <- read_csv(here("data/criteria_pairwise_null_difference_distributions.csv"))
Rows: 10000 Columns: 15── Column specification ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
dbl (15): mcas_1.mcas_2, mcas_1.aha_kawasaki, mcas_1.eular_acr_sle, mcas_1.migraine, mcas_1.slicc_sle, mcas_2.aha_kawasaki, mcas_2.eular_acr_sle, mcas_2.migraine, mcas_2.slicc_sle, aha_kawasaki.eular_...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
pairs_permutation_distributions
# Create a df of the actual difference in diversity between pairs of criteria
pairs_entropy_diff <- lapply(pairs_list, permutation_pair_iteration, randomize = F)
names(pairs_entropy_diff) <- lapply(pairs_list, function(x) paste(x, collapse = "-"))
pairs_entropy_diff <- data.frame(pairs_entropy_diff) %>%  
  pivot_longer(everything(), names_to = "pair", values_to = "entropy_difference") %>% 
  mutate(entropy_difference = ifelse(entropy_difference >=0, 0-entropy_difference, entropy_difference))
pairs_entropy_diff
# Create plots of null hypothesis distributions, overlaid with actual diversity difference
pairs_permutation_distributions %>%
  pivot_longer(cols = everything(), names_to = "pair", values_to = "entropy_difference") %>% 
  ggplot(aes(x = entropy_difference)) +
  geom_histogram(binwidth = 0.01)+
  geom_vline(data = pairs_entropy_diff, aes(xintercept = entropy_difference), color = "red")+
  facet_wrap(~pair, scales = "free")+
  theme_classic()

# Calculate p-values for diversity differences based on the quantile of the 
# diversity difference value compared to the null hypothesis diversity distribution values
# Pairwise comparison adjusted
data.frame(pairs_permutation_distributions) %>% 
  pivot_longer(everything(), names_to = "pair", values_to = "entropy_difference") %>% 
  group_by(pair) %>% 
  summarise(entropy_difference_distribution = list(entropy_difference)) %>% 
  right_join(pairs_entropy_diff) %>% 
  mutate(p_value = map2_dbl(entropy_difference, entropy_difference_distribution, ~ecdf(.y)(.x))) %>% 
  mutate(p_adj = p.adjust(p_value, method = "bonferroni", n = n()))
Joining with `by = join_by(pair)`
LS0tCnRpdGxlOiAiQ29tcGFyaW5nIE1DQVMgY3JpdGVyaWEgdG8gb3RoZXIgY29uZGl0aW9ucyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGhlcmUpCmxpYnJhcnkodmVnYW4pCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KGJyb29tKQpzb3VyY2UoaGVyZSgidXRpbF9mdW5jdGlvbnMuUiIpKQpgYGAKCiMjIFJlYWQgZGF0YQoKYGBge3J9CmNyaXRlcmlhX3BhdGggPC0gaGVyZSgiZGF0YS9kaXNlYXNlX2NyaXRlcmlhIikKYGBgCgpgYGB7cn0KcGFyc2VfZGlhZ25vc2VzIDwtIGZ1bmN0aW9uKHNpbV9yZXN1bHRzKXsKICAKICB0aWJibGUoY3JpdGVyaWEgPSByb3duYW1lcyhzaW1fcmVzdWx0cykpICU+JSAKICAgIG11dGF0ZShkYXRhID0gbWFwKGNyaXRlcmlhLCBmdW5jdGlvbihjKXsKICAgICAgZG8uY2FsbChyYmluZCwgc2ltX3Jlc3VsdHNbYyxdKSAlPiUgCiAgICAgICAgbXV0YXRlKGRpYWdub3NpcyA9IG1hcChkaWFnbm9zZXMsIHByb2Nlc3NfZGlhZ25vc2VzKSkgJT4lIAogICAgICAgIHVubmVzdChkaWFnbm9zaXMpICU+JSAKICAgICAgICBzZWxlY3QoZGlhZ25vc2lzKQogICAgICB9KSkgJT4lIAogICAgdW5uZXN0KGRhdGEpCiAgICAgICAgIAp9CmBgYAoKYGBge3J9CiMgUmVhZCBpbiBDaGF0R1BUIG91dHB1dCBkYXRhIGZvciBhZGRpdGlvbmFsIGNvbmRpdGlvbnMgYW5kIHBhcnNlCmRmIDwtIHRpYmJsZShmaWxlID0gbGlzdC5maWxlcyhoZXJlKCJkYXRhL2NoYXRncHRfb3V0cHV0L290aGVyX2NvbmRpdGlvbnMiKSwgZnVsbC5uYW1lcyA9IFQpKSU+JSAKICBtdXRhdGUoZGF0YSA9IG1hcChmaWxlLCByZWFkUkRTKSkgJT4lIAogIG11dGF0ZShkYXRhID0gbWFwKGRhdGEsIHBhcnNlX2RpYWdub3NlcykpICU+JSAKICB1bm5lc3QoZGF0YSkgJT4lIAogIHNlbGVjdCgtZmlsZSkKCiMgUmVhZCBpbiBvcmlnaW5hbCBNQ0FTIGNoYXRHUFQgb3V0cHV0IGFuZCBhcHBlbmQgdG8gZGF0YSBmcm9tIGFkZGl0aW9uYWwgY29uZGl0aW9ucwpkZiA8LSByZWFkX2NzdihoZXJlKCJkYXRhL2NvbXBpbGVkX21jYXNfY2hhdGdwdF9kaWFnbm9zZXMuY3N2IikpICU+JSAKICBtdXRhdGUoY3JpdGVyaWEgPSBnc3ViKCJjb25zZW5zdXMiLCAibWNhc18iLCBjb25zZW5zdXMpKSAlPiUgCiAgc2VsZWN0KGNyaXRlcmlhLCBkaWFnbm9zaXMpICU+JSAKICBiaW5kX3Jvd3MoZGYpIAoKZGYKYGBgCiMjIFBsb3QgZGlzdHJpYnV0aW9ucyAKCmBgYHtyfQojIFJhbmsgYWJ1bmRhbmNlIHBsb3QKY3VzdG9tX3BhbCA8LSBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoNywgIlNldDEiKVstNl0KCmRmICU+JSAKICBjb3VudChjcml0ZXJpYSwgZGlhZ25vc2lzLCBzb3J0ID0gVCkgJT4lIAogIGdyb3VwX2J5KGNyaXRlcmlhKSAlPiUgCiAgbXV0YXRlKGZyZXEgPSBuL3N1bShuKSkgJT4lIAogIGFycmFuZ2UoZGVzYyhmcmVxKSwgLmJ5X2dyb3VwID0gVCkgJT4lIAogIG11dGF0ZShyYW5rID0gMTpuKCkpICU+JSAKICBzZWxlY3QoY3JpdGVyaWEsIGZyZXEsIHJhbmspICU+JSAKICBtdXRhdGUoY3MgPSBjdW1zdW0oZnJlcSkpICU+JSAKICBmaWx0ZXIocmFuayA8PSA1MCkgJT4lIAogIG11dGF0ZShjcml0ZXJpYSA9IHRvdXBwZXIoY3JpdGVyaWEpKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gcmFuaywgeSA9IGZyZXEsIGNvbG9yID0gZmFjdG9yKGNyaXRlcmlhKSkpKwogICAgZ2VvbV9saW5lKCkgKwogICAgdGhlbWVfY2xhc3NpYygpKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGN1c3RvbV9wYWwpKwogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsMCkpKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCkpICsKICBsYWJzKHggPSAiRGlhZ25vc2lzIHJhbmsiLCB5ID0gIkRpYWdub3NpcyBmcmVxdWVuY3kiLCBjb2xvciA9ICIiKQpgYGAKIyMgQ2FsY3VsYXRlIGFuZCBwbG90IGRpdmVyc2l0eQoKYGBge3J9CiMgQ2FsY3VsYXRlIGRpdmVyc2l0eSB2YWx1ZXMgZm9yIGRpYWdub3NpcyBkaXN0cmlidXRpb25zCmRpYWdub3Npc190YWJsZSA8LSB0YWJsZShkZiRjcml0ZXJpYSwgZGYkZGlhZ25vc2lzKQpkaXZfZGlhZyAgPC0gdmVnYW46OmRpdmVyc2l0eShkaWFnbm9zaXNfdGFibGUpCmRpdl9kaWFnICAKYGBgCgpgYGB7ciwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodD01fQojIFBsb3QgZGl2ZXJzaXR5IHZhbHVlcwpicm9vbTo6dGlkeShkaXZfZGlhZykgJT4lIAogIG11dGF0ZShuYW1lcyA9IHRvdXBwZXIobmFtZXMpKSAlPiUgCiAgZ2dwbG90KGFlcyh4PW5hbWVzLCB5ID0geCkpKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWVfYncoKSArCiAgbGFicyh4ID0gIiIsIHkgPSAiU2hhbm5vbiBkaXZlcnNpdHkiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKYGBgCgojIyBTdGF0aXN0aWNhbCB0ZXN0aW5nCgojIyMgQWxsIGdyb3VwIHJhbmRvbWl6YXRpb24KCmBgYHtyfQpwYWlyd2lzZV9kaXZfZGlmZmVyZW5jZSA8LSBmdW5jdGlvbihkZil7CiAgZGZfZGl2IDwtIGVuZnJhbWUodmVnYW46OmRpdmVyc2l0eSh0YWJsZShkZiRjcml0ZXJpYSwgZGYkZGlhZ25vc2lzKSkpCiAgZGZfZGlmZiA8LSBkYXRhLmZyYW1lKHQoY29tYm4odW5pcXVlKHNvcnQoZGYkY3JpdGVyaWEpKSwyKSkpICU+JSAKICAgIGxlZnRfam9pbihkZl9kaXYsIGJ5ID0gYygiWDEiPSJuYW1lIikpICU+JSAKICAgIGxlZnRfam9pbihkZl9kaXYsIGJ5ID0gYygiWDIiPSJuYW1lIikpICU+JSAKICAgIHVuaXRlKCJwYWlyIiwgWDEsIFgyLCBzZXAgPSAiLiIpICU+JQogICAgbXV0YXRlKGVudHJvcHlfZGlmZmVyZW5jZSA9IHZhbHVlLnggLSB2YWx1ZS55KSAlPiUgCiAgICBzZWxlY3QoLWNvbnRhaW5zKCJ2YWx1ZSIpKQogIGRlZnJhbWUoZGZfZGlmZikKfQoKIyBwYWlyd2lzZV9kaXZfZGlmZmVyZW5jZShkZikKZW5mcmFtZShwYWlyd2lzZV9kaXZfZGlmZmVyZW5jZShkZiksIG5hbWUgPSAicGFpciIsIHZhbHVlID0gImVudHJvcHlfZGlmZmVyZW5jZSIpCmBgYAoKYGBge3J9CnBhaXJ3aXNlX2Rpdl9pdGVyYXRpb24gPC0gZnVuY3Rpb24oKXsKICBkZiAlPiUgCiAgICBjb3VudChjcml0ZXJpYSkgJT4lIAogICAgbXV0YXRlKGRpYWdub3NpcyA9IG1hcChuLCBmdW5jdGlvbih4KSBzYW1wbGUoZGYkZGlhZ25vc2lzLCB4LCByZXBsYWNlID0gVCkpKSAlPiUgCiAgICBzZWxlY3QoLW4pICU+JSAKICAgIHVubmVzdChkaWFnbm9zaXMpICU+JSAKICAgIHBhaXJ3aXNlX2Rpdl9kaWZmZXJlbmNlKC4pCn0KCnBhaXJ3aXNlX2Rpdl9pdGVyYXRpb24oKQpgYGAKCmBgYHtyfQpzZXQuc2VlZCgxMjM0KQpwYWlyc19wZXJtdXRhdGlvbl9kaXN0cmlidXRpb25zIDwtIGRhdGEuZnJhbWUodChyZXBsaWNhdGUoMTAwMDAsIHBhaXJ3aXNlX2Rpdl9pdGVyYXRpb24oKSkpKQpgYGAKYGBge3J9CnNhcHBseShjb21ibih1bmlxdWUoc29ydChkZiRjcml0ZXJpYSkpLDIsIHNpbXBsaWZ5ID0gRiksIHBhc3RlLCBjb2xsYXBzZSA9ICIuIikKbmFtZXMocGFpcnNfcGVybXV0YXRpb25fZGlzdHJpYnV0aW9ucykKYGBgCmBgYHtyfQpwYWlyc19lbnRyb3B5X2RpZmYgPC0gZW5mcmFtZShwYWlyd2lzZV9kaXZfZGlmZmVyZW5jZShkZiksIG5hbWUgPSAicGFpciIsIHZhbHVlID0gImVudHJvcHlfZGlmZmVyZW5jZSIpICU+JSAKICBtdXRhdGUoZW50cm9weV9kaWZmZXJlbmNlID0gaWZlbHNlKGVudHJvcHlfZGlmZmVyZW5jZSA+PTAsIDAtZW50cm9weV9kaWZmZXJlbmNlLCBlbnRyb3B5X2RpZmZlcmVuY2UpKQpwYWlyc19lbnRyb3B5X2RpZmYKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0ID0gOH0KcGFpcnNfcGVybXV0YXRpb25fZGlzdHJpYnV0aW9ucyAlPiUgCiAgcGl2b3RfbG9uZ2VyKGV2ZXJ5dGhpbmcoKSwgbmFtZXNfdG8gPSAicGFpciIsIHZhbHVlc190byA9ICJlbnRyb3B5X2RpZmZlcmVuY2UiKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gZW50cm9weV9kaWZmZXJlbmNlKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wMSkrCiAgZ2VvbV92bGluZShkYXRhID0gcGFpcnNfZW50cm9weV9kaWZmLCBhZXMoeGludGVyY2VwdCA9IGVudHJvcHlfZGlmZmVyZW5jZSksIGNvbG9yID0gInJlZCIpKwogIGZhY2V0X3dyYXAofnBhaXIsIHNjYWxlcyA9ICJmcmVlIikrCiAgdGhlbWVfY2xhc3NpYygpCmBgYAoKYGBge3J9CiMgQ2FsY3VsYXRlIHAtdmFsdWVzIGZvciBkaXZlcnNpdHkgZGlmZmVyZW5jZXMgYmFzZWQgb24gdGhlIHF1YW50aWxlIG9mIHRoZSAKIyBkaXZlcnNpdHkgZGlmZmVyZW5jZSB2YWx1ZSBjb21wYXJlZCB0byB0aGUgbnVsbCBoeXBvdGhlc2lzIGRpdmVyc2l0eSBkaXN0cmlidXRpb24gdmFsdWVzCiMgUGFpcndpc2UgY29tcGFyaXNvbiBhZGp1c3RlZAplbnRyb3B5X2RpZmZfdGFibGUgPC0gZGF0YS5mcmFtZShwYWlyc19wZXJtdXRhdGlvbl9kaXN0cmlidXRpb25zKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKGV2ZXJ5dGhpbmcoKSwgbmFtZXNfdG8gPSAicGFpciIsIHZhbHVlc190byA9ICJlbnRyb3B5X2RpZmZlcmVuY2UiKSAlPiUgCiAgZ3JvdXBfYnkocGFpcikgJT4lIAogIHN1bW1hcmlzZShlbnRyb3B5X2RpZmZlcmVuY2VfZGlzdHJpYnV0aW9uID0gbGlzdChlbnRyb3B5X2RpZmZlcmVuY2UpKSAlPiUgCiAgcmlnaHRfam9pbihwYWlyc19lbnRyb3B5X2RpZmYpICU+JSAKICBtdXRhdGUocF92YWx1ZSA9IG1hcDJfZGJsKGVudHJvcHlfZGlmZmVyZW5jZSwgZW50cm9weV9kaWZmZXJlbmNlX2Rpc3RyaWJ1dGlvbiwgfmVjZGYoLnkpKC54KSkpICU+JSAKICBtdXRhdGUocF9hZGogPSBwLmFkanVzdChwX3ZhbHVlLCBtZXRob2QgPSAiYm9uZmVycm9uaSIsIG4gPSBuKCkpKQplbnRyb3B5X2RpZmZfdGFibGUKYGBgCmBgYHtyLCBmaWcud2lkdGggPSA1LCBmaWcuaGVpZ2h0PTV9CiMgUGxvdCBkaXZlcnNpdHkgdmFsdWVzCmJyb29tOjp0aWR5KGRpdl9kaWFnKSAlPiUgCiAgbXV0YXRlKG5hbWVzID0gdG9sb3dlcihuYW1lcykpICU+JSAKICBnZ3Bsb3QoYWVzKHg9bmFtZXMsIHkgPSB4KSkrCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICBnZ3B1YnI6OnN0YXRfcHZhbHVlX21hbnVhbCgKICAgIGVudHJvcHlfZGlmZl90YWJsZSAlPiUgc2VwYXJhdGUocGFpciwgaW50byA9IGMoImdyb3VwMSIsICJncm91cDIiKSwgc2VwID0gIlxcLiIpLAogICAgbGFiZWw9ICJwX2FkaiIsCiAgICB5LnBvc2l0aW9uID0gNywgc3RlcC5pbmNyZWFzZSA9IDAuMgogICkgKwogIHRoZW1lX2J3KCkgKwogIGxhYnMoeCA9ICIiLCB5ID0gIlNoYW5ub24gZGl2ZXJzaXR5IikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCmBgYAoKCgojIyMgUGFpcndpc2UgcmFuZG9taXphdGlvbgoKYGBge3J9CnBhaXJzX2xpc3QgPC0gYXMubGlzdChhcy5kYXRhLmZyYW1lKGNvbWJuKHVuaXF1ZShkZiRjcml0ZXJpYSksMikpKQpuYW1lcyhwYWlyc19saXN0KSA8LSBzYXBwbHkocGFpcnNfbGlzdCwgcGFzdGUsIGNvbGxhcHNlID0gIi4iKQpwYWlyc19saXN0CmBgYAoKCgpgYGB7cn0KIyBDcmVhdGUgZnVuY3Rpb24gdGhhdCBzdWJzZXRzIGRmIG9mIGFsbCBkaWFnbm9zZXMgZm9yIGEgY3JpdGVyaWEgYmFzZWQgb24gYSBwYWlyIG9mIHR3byBjcml0ZXJpYSB0byBjb21wYXJlCiMgVGhlbiByYW5kb21pemVzIHRoZSBkaWFnbm9zZXMgYWNyb3NzIHRoZSB0d28gY3JpdGVyaWEsIAojIG1lYXN1cmVzIHRoZSByZXN1bHRpbmcgZGl2ZXJzaXR5IG9mIGVhY2ggY3JpdGVyaWEncyBkaWFnbm9zZXMKIyBhbmQgY2FsY3VsYXRlcyBhIGRpZmZlcmVuY2UgaW4gdGhlIGRpdmVyc2l0eSB2YWx1ZQojIHJhbmRvbWl6ZSBjYW4gYmUgc2V0IEYgdG8gb2J0YWluIG9yaWdpbmFsIGRpZmZlcmVuY2UgaW4gZGl2ZXJzaXR5IHcvbyByYW5kb21pemF0aW9uCiMgVG8gYmUgdXNlZCB3aXRoIHJlcGxpY2F0ZSB0byBnZW5lcmF0ZSBhIG51bGwgaHlwb3RoZXNpcyBkaXN0cmlidXRpb24gCnBlcm11dGF0aW9uX3BhaXJfaXRlcmF0aW9uIDwtIGZ1bmN0aW9uKHBhaXIsIHJhbmRvbWl6ZSA9IFQpewogIGRmIDwtIGRmICU+JSAKICAgIGZpbHRlcihjcml0ZXJpYSAlaW4lIHBhaXIpIAogIGlmIChyYW5kb21pemUgPT0gVCl7CiAgICBkZiA8LSBkZiAlPiUgbXV0YXRlKGRpYWdub3NpcyA9IHNhbXBsZShkaWFnbm9zaXMsIG4oKSkpCiAgfQogIGRpdiA8LSB2ZWdhbjo6ZGl2ZXJzaXR5KHRhYmxlKGRmJGNyaXRlcmlhLCBkZiRkaWFnbm9zaXMpKQogIHVubmFtZShkaXZbMV0gLSBkaXZbMl0pCn0KCnJlcGxpY2F0ZSgxMCxwZXJtdXRhdGlvbl9wYWlyX2l0ZXJhdGlvbihwYWlyc19saXN0W1sxXV0sIHJhbmRvbWl6ZSA9IFQpKQpyZXBsaWNhdGUoMTAscGVybXV0YXRpb25fcGFpcl9pdGVyYXRpb24ocGFpcnNfbGlzdFtbMV1dLCByYW5kb21pemUgPSBGKSkKYGBgCgpgYGB7cn0KIyAjIENyZWF0ZSBhIG51bGwgaHlwb3RoZXNpcyBkaXN0cmlidXRpb24gZm9yIHRoZSBkaWZmZXJlbmNlIGluIGRpdmVyc2l0eQojICMgRm9yIGFsbCBwYWlycyBvZiBjcml0ZXJpYQoKIyBzZXQuc2VlZCgxMjM0KQojIHBhaXJzX3Blcm11dGF0aW9uX2Rpc3RyaWJ1dGlvbnMgPC0gbGFwcGx5KHBhaXJzX2xpc3QsIGZ1bmN0aW9uKHgpIHJlcGxpY2F0ZSgxMDAwMCwgcGVybXV0YXRpb25fcGFpcl9pdGVyYXRpb24oeCkpKQojIG5hbWVzKHBhaXJzX3Blcm11dGF0aW9uX2Rpc3RyaWJ1dGlvbnMpIDwtIGxhcHBseShwYWlyc19saXN0LCBmdW5jdGlvbih4KSBwYXN0ZSh4LCBjb2xsYXBzZSA9ICItIikpCiMgZGF0YS5mcmFtZShwYWlyc19wZXJtdXRhdGlvbl9kaXN0cmlidXRpb25zKQojIHdyaXRlX2NzdihkYXRhLmZyYW1lKHBhaXJzX3Blcm11dGF0aW9uX2Rpc3RyaWJ1dGlvbnMpLCBoZXJlKCJkYXRhL2NyaXRlcmlhX3BhaXJ3aXNlX251bGxfZGlmZmVyZW5jZV9kaXN0cmlidXRpb25zLmNzdiIpKQoKcGFpcnNfcGVybXV0YXRpb25fZGlzdHJpYnV0aW9ucyA8LSByZWFkX2NzdihoZXJlKCJkYXRhL2NyaXRlcmlhX3BhaXJ3aXNlX251bGxfZGlmZmVyZW5jZV9kaXN0cmlidXRpb25zLmNzdiIpKQpwYWlyc19wZXJtdXRhdGlvbl9kaXN0cmlidXRpb25zCmBgYAoKCmBgYHtyfQojIENyZWF0ZSBhIGRmIG9mIHRoZSBhY3R1YWwgZGlmZmVyZW5jZSBpbiBkaXZlcnNpdHkgYmV0d2VlbiBwYWlycyBvZiBjcml0ZXJpYQpwYWlyc19lbnRyb3B5X2RpZmYgPC0gbGFwcGx5KHBhaXJzX2xpc3QsIHBlcm11dGF0aW9uX3BhaXJfaXRlcmF0aW9uLCByYW5kb21pemUgPSBGKQpuYW1lcyhwYWlyc19lbnRyb3B5X2RpZmYpIDwtIGxhcHBseShwYWlyc19saXN0LCBmdW5jdGlvbih4KSBwYXN0ZSh4LCBjb2xsYXBzZSA9ICItIikpCnBhaXJzX2VudHJvcHlfZGlmZiA8LSBkYXRhLmZyYW1lKHBhaXJzX2VudHJvcHlfZGlmZikgJT4lICAKICBwaXZvdF9sb25nZXIoZXZlcnl0aGluZygpLCBuYW1lc190byA9ICJwYWlyIiwgdmFsdWVzX3RvID0gImVudHJvcHlfZGlmZmVyZW5jZSIpICU+JSAKICBtdXRhdGUoZW50cm9weV9kaWZmZXJlbmNlID0gaWZlbHNlKGVudHJvcHlfZGlmZmVyZW5jZSA+PTAsIDAtZW50cm9weV9kaWZmZXJlbmNlLCBlbnRyb3B5X2RpZmZlcmVuY2UpKQpwYWlyc19lbnRyb3B5X2RpZmYKYGBgCmBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQgPSA4fQojIENyZWF0ZSBwbG90cyBvZiBudWxsIGh5cG90aGVzaXMgZGlzdHJpYnV0aW9ucywgb3ZlcmxhaWQgd2l0aCBhY3R1YWwgZGl2ZXJzaXR5IGRpZmZlcmVuY2UKcGFpcnNfcGVybXV0YXRpb25fZGlzdHJpYnV0aW9ucyAlPiUKICBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwgbmFtZXNfdG8gPSAicGFpciIsIHZhbHVlc190byA9ICJlbnRyb3B5X2RpZmZlcmVuY2UiKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gZW50cm9weV9kaWZmZXJlbmNlKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wMSkrCiAgZ2VvbV92bGluZShkYXRhID0gcGFpcnNfZW50cm9weV9kaWZmLCBhZXMoeGludGVyY2VwdCA9IGVudHJvcHlfZGlmZmVyZW5jZSksIGNvbG9yID0gInJlZCIpKwogIGZhY2V0X3dyYXAofnBhaXIsIHNjYWxlcyA9ICJmcmVlIikrCiAgdGhlbWVfY2xhc3NpYygpCmBgYApgYGB7cn0KIyBDYWxjdWxhdGUgcC12YWx1ZXMgZm9yIGRpdmVyc2l0eSBkaWZmZXJlbmNlcyBiYXNlZCBvbiB0aGUgcXVhbnRpbGUgb2YgdGhlIAojIGRpdmVyc2l0eSBkaWZmZXJlbmNlIHZhbHVlIGNvbXBhcmVkIHRvIHRoZSBudWxsIGh5cG90aGVzaXMgZGl2ZXJzaXR5IGRpc3RyaWJ1dGlvbiB2YWx1ZXMKIyBQYWlyd2lzZSBjb21wYXJpc29uIGFkanVzdGVkCmRhdGEuZnJhbWUocGFpcnNfcGVybXV0YXRpb25fZGlzdHJpYnV0aW9ucykgJT4lIAogIHBpdm90X2xvbmdlcihldmVyeXRoaW5nKCksIG5hbWVzX3RvID0gInBhaXIiLCB2YWx1ZXNfdG8gPSAiZW50cm9weV9kaWZmZXJlbmNlIikgJT4lIAogIGdyb3VwX2J5KHBhaXIpICU+JSAKICBzdW1tYXJpc2UoZW50cm9weV9kaWZmZXJlbmNlX2Rpc3RyaWJ1dGlvbiA9IGxpc3QoZW50cm9weV9kaWZmZXJlbmNlKSkgJT4lIAogIHJpZ2h0X2pvaW4ocGFpcnNfZW50cm9weV9kaWZmKSAlPiUgCiAgbXV0YXRlKHBfdmFsdWUgPSBtYXAyX2RibChlbnRyb3B5X2RpZmZlcmVuY2UsIGVudHJvcHlfZGlmZmVyZW5jZV9kaXN0cmlidXRpb24sIH5lY2RmKC55KSgueCkpKSAlPiUgCiAgbXV0YXRlKHBfYWRqID0gcC5hZGp1c3QocF92YWx1ZSwgbWV0aG9kID0gImJvbmZlcnJvbmkiLCBuID0gbigpKSkKYGBgCg==